﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

// 文件操作

// 我们为什么要使用文件呢？

// 1.可以长久的存储我们的数据，没有文件存储我们输入的程序代码数据是在电脑
//	 内存中存储的，但是程序结束后，我们的数据也会清空，无法持久化保存

//如以下代码
#if 0
int main()
{
	int a = 0; //创建变量a
	scanf("%d", &a); // 向a中输入数值
	return 0;
}
#endif
// 我们可以通过内存窗口看见int变量a中存放我们输入的值，但是程序结束，数据会清空，我们需要重新输入


// 那么什么是文件呢？

// 文件指的就是磁盘上的文件
// 但是程序中我们的文件分为两种:程序文件，数据文件 - 按照功能划分

// 首先来了解程序文件
// struct 程序文件
//{
//	1.源程序文件(后缀为.c);
//  2.目标文件(windows环境后缀为.obj);
//  3.可执行文件(windows环境后缀为.exe);
//}；

// 其次是数据文件
// 该文件的内容不一定是程序，也可以是运行时读写的数据，比如程序运⾏需要从中读取数据的⽂件，
// 或者输出内容的文件。


// 文件名字
// 用于用户识别和使用，是文件的标识
// 这个相信大家都不陌生，我们在打开磁盘可以在上方的小方块看到当期文件的名字。

// 二进制文件和文本文件

//根据数据不同形式，我们将数据文件划分为二进制文件和文本文件

//二进制文件：以二进制的形式存储，不用转换直接可以输出到外存

//文本文件：字符一律以ASCLL码值存储，需要转换才可以输出到外存


// 文件的打开和关闭

// 在了解文件之前，我们需要认识一种抽象的概念 - 流
// 什么是流呢？我们在生活中获取消息是从人流，网上等等渠道，同理计算机也是如此，
// 我们把从键盘输入，文件读写和其他所有外部的数据糅合起来比作一条流淌着字符的数据河，
// 而计算机则是从河中获取所需要的数据，读取它们，然后打开，再操作数据

// 那么我们有可能有疑问了？我们平时键盘输入代码，屏幕打印字符数据好像并没有使用流啊
// 这里我们就需要引入一个概念 - 标准流
// 标准流在计算机C程序运行启动时就默认打开了3个流：

// 1.stdin  - 标准输入流，获取我们输入的信息。
//2.stdout - 标准输出流，输出我们需要打印的信息
// 3.stderr - 标准错误流。

// 有了以上默认打开的流，我们使用scanf和printf就可以进行输入输出操作

// stdin，stdout，stderr 类型为 FILE* - 文件指针
// C语⾔中，就是通过FILE*的⽂件指针来维护流的各种操作的。

// 文件指针

// 我们在打开使用文件时，内存中会开辟一个文件信息区，这个信息区用来存放文件的相关
// 信息，这些信息是保存在一个结构体变量中，取名为FILE。

// 如vs2013编译环境中#include <stdio.h> 头文件种的类型申明。
#if 0
struct _iobuf {
	char* _ptr;
	int _cnt;
	char* _base;
	int _flag;
	int _file;
	int _charbuf;
	int _bufsiz;
	char* _tmpfname;
};
typedef struct _iobuf FILE;
#endif

// 借助这点我们可以定义一个指针变量指向该文件信息区，间接的找到该文件

// FILE* pf; //文件指针变量


// 文件的打开和关闭

// 在读写编辑文件之前，我们需要学会如何打开和关闭文件

// 1.打开文件

// 在编写程序时在打开⽂件的同时，都会返回⼀个FILE*的指针变量指向该⽂件，也相当于建⽴了
// 指针和⽂件的关系。

// ANSIC规定使⽤ fopen 函数来打开⽂件， fclose 来关闭⽂件

// 代码演示：

// 打开文件：
//FILE* fopen(const char* filename, const char* mode);

// 关闭文件：
//int fclose(FILE* stream);

// mode - 文件打开的模式，所有模式如下：

// "r"(只读) - 输入数据，打开一个存在的文本文件 - 如果文件不存在（出错）

// "w"(只写) - 输出数据，打开一个文本文件 - 如果文件不存在（创建一个新的文件）

// "a"(追加) - 向文本文件末尾添加数据 - 如果文件不存在（创建一个新的文件）

// "rb"(只读) - 输入数据，打开一个二进制文本文件 - 如果文件不存在（出错）

// "wb"(只写) - 输出数据，打开一个二进制文本文件 - 如果文件不存在（创建一个新的文件）

// "ab"(追加) - 向二进制文本文件末尾添加数据 - 如果文件不存在（创建一个新的文件）

// "r+"(读写) - 为了读和写，打开一个文本文件 - 如果文件不存在（出错）

// "w+"(读写）- 为了读和写，创建一个文本文件 - 如果文件不存在（创建一个新的文件）

// "a+"(读写）- 打开一个文本文件，在文件末尾进行读写 - 如果文件不存在（创建一个新的文件）

// "rb+"(读写）- 为了读和写，打开一个二进制文本文件 - 如果文件不存在（出错）

// "wb+"(读写）- 为了读和写，创建一个二进制文本文件 - 如果文件不存在（创建一个新的文件）

// "ab+"(读写）- 打开一个二进制文本文件，在文件末尾进行读写 - 如果文件不存在（创建一个新的文件）

#if 0
int main()
{
	// 创建文本指针变量
	FILE* pf;
	//打开文件
	pf = fopen("text.txt", "r");
	//文件操作
	if (pf == NULL)
	{
		//...
		perror("fopen");
		return 1;
	}
	else
	{
		fclose(pf);
		pf = NULL;
	}
	return 0;
}
#endif


// 文件的顺序读写

// 1.顺序读写函数介绍

//  函数    功能          适用于

// fgetc - 字符输入函数 - 所有的输入流

// fputc - 字符输出函数 - 所有的输出流

// fgets - 文本行输入函数 - 所有输入流

// fputs - 文本行输入函数 - 所有输入流

// fscnaf - 格式化输入函数 - 所有输入流

// fprintf - 格式化输出函数 - 所有输出流

// fread   -   二进制输入   -   文件

// fwrite   -   二进制输出   -   文件


// 实验1，打开一个文件并且输入26个字母
#if 0
int main()
{
	// 打开并且写一个文件 - "w"
	FILE* pf = fopen("text.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//写文件
	int ch = 0;
	for (ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, pf);
	}

	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
#endif

// 打开读取文件text.txt ,并且将它拷贝进text_cpy.txt
#if 0
int main()
{
	// 打开文件
	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)
	{
		perror("fopen text.txt");
		return 1;
	}

	FILE* pw = fopen("text_cpy.txt", "w");
	if (pw == NULL)
	{
		perror("fopen text_cpy.txt");
		fclose(pf);
		pf = NULL;
		return 1;
	}

	// 读取并且写入
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		fputc(ch, pw);
	}

	// 关闭文件
	fclose(pf);
	pf = NULL;
	fclose(pw);
	pw = NULL;
	return 0;
}
#endif

// 使用 fputs 和 fgets 函数
#if 0
int main()
{
	// 打开文件
	FILE* pf = fopen("fputs.txt", "w");
	if (pf == NULL)
	{
		perror("fopen fputs.txt");
		return 1;
	}

	// 使用 fputs函数写文件
	// 函数原型
	// int fputs(const char* str, FILE * stream);
	// const char* str 传入读取的字符串的首地址
	// FILE* stream 将读取的内容写入文件中

	char ch[] = "ouyang is cool\n";
	fputs(ch, pf);
	fputs("hello ouyang", pf);

	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

int main()
{
	// 打开文件
	FILE* pf = fopen("fputs.txt", "r");
	if (pf == NULL)
	{
		perror("fopen fputs.txt");
		return 1;
	}

	// 使用 fgets函数读取文件
	// 函数原型
	// char* fgets(char* str, int num, FILE * stream);
	// char* str 函数将读取到的数据返回char*类型指针的首地址
	// int num  读取的长度
	// FILE* stream 读取文件的地址
	// 如果读取失败会返回NULL

	char ch[20] = { 0 };

	// 单个读取
	/*fgets(ch, 20, pf);
	printf("%s", ch);*/

	// 连续读取
	while (fgets(ch, 20, pf) != NULL)
	{
		printf("%s", ch);
	}
	
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
#endif


// 使用 fprintf函数和 fscanf 函数
#if 0
typedef struct stu
{
	char name[20];
	int age;
	char sex[5];
}S;
int main()
{
	// 打开文件并且写入
	FILE* pf = fopen("fprintf.txt", "w");
	if (pf == NULL)
	{
		perror("fopen fprintf.txt");
		return 1;
	}

	// 写入
	S s = { "欧阳",20,"男" };
	fprintf(pf, "%s %d %s", s.name, s.age, s.sex);


	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
#endif

#if 0
typedef struct stu
{
	char name[20];
	int age;
	char sex[5];
}S;
int main()
{
	// 打开文件并且写入
	FILE* pf = fopen("fprintf.txt", "r");
	if (pf == NULL)
	{
		perror("fopen fprintf.txt");
		return 1;
	}

	// 读取文件
	S s = {0};
	fscanf(pf, "%s %d %s", s.name, &(s.age), s.sex);

	// 打印在屏幕上观察
	printf("%s %d %s", s.name, s.age, s.sex);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
#endif

// 有关于流的概念


/*stdin - 标准输入流，获取我们输入的信息。
stdout - 标准输出流，输出我们需要打印的信息

fgetc, fgets, fputc, fputs, fscanf, fprintf - 适用于所有流

fread, fwrite - 适用于文件流*/

// 使用标准流
#if 0
int main()
{
	fputc('a', stdout);
	return 0;
}
#endif

// 使用 fwrite 和 fread 函数
#if 0
int main()
{
	// 以二进制的形式写文件
	FILE* pf = fopen("fwrite.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen fwrite.txt");
		return 1;
	}

	// 写二进制文件
	
	// fwrite 函数
	// 函数原型：
	// size_t fwrite(const void* ptr, size_t size, size_t count, FILE * stream);
	// const void* ptr - 指向要写入文件的数据
	// size_t size - 指向数据类型的大小
	// siez_t count -指向数据的数量
	// FILE * stream - 写入的文件指针

	int arr[] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	fwrite(arr, sizeof(arr[0]), sz, pf);

	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
#endif

#if 0
int main()
{
	// 打开文件
	FILE* pf = fopen("fwrite.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen fwrite.txt");
		return 1;
	}

	// 读取文件
	// fread 函数
	// 函数原型：
	// size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
	// const void* ptr - 指向要读取文件的数据
	// size_t size - 指向数据类型的大小
	// siez_t count -指向数据的数量
	// FILE * stream - 要读取的文件指针
	int arr[5] = { 0 };
	fread(arr, sizeof(arr[0]), 5, pf);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}

	// 关闭文件
	fclose(pf);
	pf == NULL;
	return 0;
}
#endif

// 文件的随机读写 - 适用于二进制文件

// fseek 函数

// 函数原型：
// int fseek ( FILE * stream, long int offset, int origin );

// FILE * stream - 指向FILE*文件对象的指针
// long int offset - 偏移的字节数
// int origin - 参考位置
// 如：
//SEEK_SET	文件开头
//SEEK_CUR	文件指针的当前位置
//SEEK_END	文件末尾*

#if 0
int main()
{
	// 读写文件
	FILE* pf = fopen("fseek.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen fseek.txt");
		return 1;
	}

	// 读写
	fputs("hello ouyang",pf);
	fseek(pf, 6, SEEK_SET);
	fputs("to ", pf);

	// 关闭文件
	fclose(pf);
	pf == NULL;
	return 0;
}
#endif

#if 0
int main()
{
	// 打开并且读文件
	FILE* pf = fopen("fseek.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen fseek.txt");
		return 1;
	}

	// 读取文件
	char ch[200] = { 0 };
	while (fgets(ch, 50, pf) != NULL)
	{
		printf("%s\n", ch);
	}

	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
#endif


// ftell 函数
// 函数原型：
// long int ftell ( FILE * stream );
#if 0
int main()
{
	// 读写文件
	FILE* pf = fopen("fseek.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen fseek.txt");
		return 1;
	}

	// ftell 函数的使用
	fseek(pf, 6, SEEK_SET);
	long size = ftell(pf);
	printf("Size of myfile.txt: %ld bytes.\n", size);

	// 关闭文件
	fclose(pf);
	pf == NULL;
	return 0;
}
#endif

// rewind 函数
// 函数原型：
// void rewind ( FILE * stream );
#if 0
int main()
{

	// 打开文件并且写入
	FILE* pf = fopen("myfile.txt", "w+");
	char buffer[27];
	int n = 0;
	for (n = 'A'; n <= 'Z'; n++)
		fputc(n, pf); //写入之后的光标在最后
	rewind(pf); // 回调光标
	fread(buffer, 1, 26, pf); // 将文件中的数据读取到字符数组中

	//关闭文件
	fclose(pf);
	pf = NULL;

	// 打印观察
	buffer[26] = '\0'; // 方便输出字符串，将最后一位设置为'\0'
	printf(buffer); // 打印
	return 0;
}
#endif
// 文件读取结束的判定

// feof 函数
// 注意：该函数不可以通过该函数的返回值来直接判断文件的结束

// 作用是当文件结束时，判断是读取失败的原因，还是遇到文件结尾结束

//⽂本⽂件读取是否结束，判断返回值是否为 EOF （ fgetc ），或者 NULL （ fgets ）
//例如：
//• fgetc 判断是否为 EOF 
//• fgets 判断返回值是否为 NULL 

#if 0
int main()
{
	int c = 0; // 注意：int，⾮char，要求处理EOF
	FILE* fp = fopen("test.txt", "r");
	if (fp==NULL) 
	{
		perror("File opening failed");
		return 1;
	}


	//fgetc 当读取失败的时候或者遇到⽂件结束的时候，都会返回EOF
	while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环
	{
		putchar(c);
	}


	//判断是什么原因结束的
	if (ferror(fp))
		puts("I/O error when reading");
	else if (feof(fp))
		puts("End of file reached successfully");
	fclose(fp);
	fp = NULL;
}
#endif

// 文件缓冲区

// 我们在程序中向文件中写入数据不会是有一条写一条，打个比方说：一个学生
// 问问题不是说问完一个过几分钟又问一个，则会导致老师无法为其他同学解答
// 问题，操作系统也是如此，操作系统不光是为了写入文件而服务的，所有这个学生
// 就会将问题攒着，直到一定的数量就可以去问老师，同理我们写入的文件也是如此
// 而存放这些数据的空间就叫做文件缓冲区